Imports System.Drawing

''' <summary>
''' The jChart.Legend object describes the properties of chart legend.
''' </summary>
''' <remarks></remarks>
Public Class Legend
    ''' <summary>
    ''' Indicates the position of the legend within the chart.
    ''' </summary>
    ''' <remarks></remarks>
    Public Enum Position As Integer
        None = 0
        North = 1
        NorthWest = 2
        West = 3
        SouthWest = 4
        South = 5
        SouthEast = 6
        East = 7
        NorthEast = 8
        Center = 9
    End Enum

    Protected Const miLeftMargin As Integer = 10
    Protected Const miRightMargin As Integer = 10
    Protected Const miTopMargin As Integer = 10
    Protected Const miBottomMargin As Integer = 10

    Protected Const miIndicatorColumnWidth As Integer = 20
    Protected Const miIndicatorMargin As Integer = 10

    Private moChartDimensions As ChartDimensions

    Protected msTitle As String
    Protected moTitleFont As Font = New Font(System.Drawing.FontFamily.GenericSansSerif, 10)

    Protected moBackgroundColor As Color = Color.DarkKhaki

    Protected moLegendFont As Font = New Font(System.Drawing.FontFamily.GenericSansSerif, 10)
    Protected moLegendColor As Color = Color.Black

    Protected miLegendPosition As Position = Position.None

    Private miMaxSeriesTextWidth As Integer
    Private miMaxSeriesTextHeight As Integer

    Private moAllSeries As New ArrayList

    Private msUniqueName As String
    Private msFileName As String

    ''' <summary>
    ''' oChartDimensions is a ChartDimensions object describing the
    ''' interior of the chart.
    ''' </summary>
    ''' <param name="oChartDimensions"></param>
    ''' <remarks></remarks>
    Public Sub New(ByVal oChartDimensions As ChartDimensions)
        moChartDimensions = oChartDimensions

        Dim oGuid As New Guid
        oGuid = Guid.NewGuid()

        msUniqueName = oGuid.ToString
    End Sub

    ''' <summary>
    ''' Adds all of the series belonging to an overlay to the legend.
    ''' This enables you to selectively
    ''' include overlays if, for example, you want the series from one
    ''' overlay to appear in
    ''' the legend but not the series from other overlays.
    ''' </summary>
    ''' <param name="oOverlay"></param>
    ''' <remarks></remarks>
    Public Sub AddOverlay( _
        ByVal oOverlay As Object)

        For Each oSeries As Object In oOverlay.Series
            Dim oEntry As New LegendEntry(oSeries)
            moAllSeries.Add(oEntry)
        Next
    End Sub

    Public Sub AddLegendEntry( _
        ByVal sText As String, _
        ByVal oIndicatorType As jChart.Indicator.Type, _
        ByVal oColor As System.Drawing.Color)

        Dim oEntry As New LegendEntry(sText, oIndicatorType, oColor)
        moAllSeries.Add(oEntry)
    End Sub

    Public Property ChartDimensions() As jChart.ChartDimensions
        Get
            Return moChartDimensions
        End Get
        Set(ByVal value As jChart.ChartDimensions)
            moChartDimensions = value
        End Set
    End Property

    Friend ReadOnly Property OverLaysExist() As Boolean
        Get
            If moAllSeries.Count > 0 Then
                Return True
            Else
                Return False
            End If
        End Get
    End Property

    ''' <summary>
    ''' The title of the legend, displayed at the top.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property Title() As String
        Get
            Return msTitle
        End Get
        Set(ByVal value As String)
            msTitle = value
        End Set
    End Property

    ''' <summary>
    ''' The font to use for the title of the legend.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property TitleFont() As Font
        Get
            Return moTitleFont
        End Get
        Set(ByVal value As Font)
            moTitleFont = value
        End Set
    End Property

    ''' <summary>
    ''' The background color of the legend.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property BackgroundColor() As Color
        Get
            Return moBackgroundColor
        End Get
        Set(ByVal value As Color)
            moBackgroundColor = value
        End Set
    End Property

    ''' <summary>
    ''' The font used for the text of the legend.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property LegendFont() As Font
        Get
            Return moLegendFont
        End Get
        Set(ByVal value As Font)
            moLegendFont = value
        End Set
    End Property

    ''' <summary>
    ''' The color used for the text of the legend.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property LegendColor() As Color
        Get
            Return moLegendColor
        End Get
        Set(ByVal value As Color)
            moLegendColor = value
        End Set
    End Property

    ''' <summary>
    ''' The position of the legend on the chart.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property LegendPosition() As Legend.Position
        Get
            Return miLegendPosition
        End Get
        Set(ByVal value As Position)
            miLegendPosition = value
        End Set
    End Property

    Public Property FileName() As String
        Get
            Return msFileName
        End Get
        Set(ByVal value As String)
            msFileName = value
        End Set
    End Property

    Friend Sub RecalculateDimensions(ByVal g As Graphics)
        Dim iHeight As Integer = 0
        Dim iMaxWidth As Integer = 0

        g.TextRenderingHint = Text.TextRenderingHint.AntiAlias
        Dim oSize As SizeF = g.MeasureString(msTitle, moTitleFont)
        g.TextRenderingHint = Text.TextRenderingHint.SystemDefault

        iMaxWidth = oSize.Width + miLeftMargin + miRightMargin
        iHeight = miTopMargin + oSize.Height + miBottomMargin

        For Each oEntry As LegendEntry In moAllSeries
            oSize = g.MeasureString(oEntry.Name, moLegendFont)

            iHeight = iHeight + oSize.Height
            If (LegendTextXPos() + oSize.Width) > iMaxWidth Then iMaxWidth = (LegendTextXPos() + oSize.Width)
        Next

        miMaxSeriesTextWidth = iMaxWidth
        miMaxSeriesTextHeight = iHeight

        Select Case miLegendPosition

            Case Position.NorthWest, Position.West, Position.SouthWest
                '---On left side
                If moChartDimensions.FrameWidthLeft < OuterWidth() Then moChartDimensions.FrameWidthLeft = OuterWidth()

            Case Position.NorthEast, Position.East, Position.SouthEast
                '---On right side
                If moChartDimensions.FrameWidthRight < OuterWidth() Then moChartDimensions.FrameWidthRight = OuterWidth()

            Case Position.North
                '---On top
                If moChartDimensions.FrameWidthTop < OuterHeight() Then moChartDimensions.FrameWidthTop = OuterHeight()

            Case Position.South
                '---On bottom
                If moChartDimensions.FrameWidthBottom < OuterHeight() Then moChartDimensions.FrameWidthBottom = OuterHeight()

            Case Position.Center

        End Select
    End Sub

    Private Function OuterWidth() As Integer
        Return moChartDimensions.OuterX + miLeftMargin + miIndicatorColumnWidth + miIndicatorMargin + miMaxSeriesTextWidth + miRightMargin
    End Function

    Private Function OuterHeight() As Integer
        Return miTopMargin + miMaxSeriesTextHeight + miBottomMargin
    End Function

    Private Function LegendTextXPos() As Integer
        Return miLeftMargin + miIndicatorColumnWidth + miIndicatorMargin
    End Function

    Private Function IndicatorXPos() As Integer
        Return miLeftMargin + miIndicatorColumnWidth / 2
    End Function

    Public Sub DrawLegend( _
                    ByVal g As Graphics)

        RecalculateDimensions(g)

        Dim oOuterArea As Rectangle = ComputeLegendArea()
        Dim oInnerArea As Rectangle = New Rectangle(oOuterArea.X + miLeftMargin, oOuterArea.Y + miTopMargin, oOuterArea.Width - (miLeftMargin + miRightMargin), oOuterArea.Height - (miTopMargin + miBottomMargin))

        '---Draw 3D rectangle around where legend will go
        Dim oBorderColorL As Color = Color.FromArgb(190, moBackgroundColor.R, moBackgroundColor.G, moBackgroundColor.B)
        Dim oBorderColorD As Color = Color.FromArgb(255, moBackgroundColor.R, moBackgroundColor.G, moBackgroundColor.B)

        'g.FillRectangle(New SolidBrush(Color.Gray), oInnerArea)
        g.FillRectangle(New SolidBrush(moBackgroundColor), oInnerArea)

        g.DrawRectangle(New Pen(Color.Black, 3), oInnerArea)

        '---left
        g.DrawLine(New Pen(oBorderColorL, 2), oInnerArea.X, oInnerArea.Y, oInnerArea.X, oInnerArea.Y + oInnerArea.Height)
        g.DrawLine(New Pen(oBorderColorD), oInnerArea.X + 2, oInnerArea.Y + 2, oInnerArea.X + 2, oInnerArea.Y + oInnerArea.Height - 2)

        '---top
        g.DrawLine(New Pen(oBorderColorL, 2), oInnerArea.X, oInnerArea.Y, oInnerArea.X + oInnerArea.Width, oInnerArea.Y)
        g.DrawLine(New Pen(oBorderColorD), oInnerArea.X + 2, oInnerArea.Y + 2, oInnerArea.X + oInnerArea.Width - 2, oInnerArea.Y + 2)

        '---right
        g.DrawLine(New Pen(oBorderColorD), oInnerArea.X + oInnerArea.Width, oInnerArea.Y + 1, oInnerArea.X + oInnerArea.Width, oInnerArea.Y + oInnerArea.Height)
        g.DrawLine(New Pen(oBorderColorL), oInnerArea.X + oInnerArea.Width - 1, oInnerArea.Y + 1, oInnerArea.X + oInnerArea.Width - 1, oInnerArea.Y + oInnerArea.Height - 1)
        g.DrawLine(New Pen(oBorderColorL), oInnerArea.X + oInnerArea.Width - 2, oInnerArea.Y + 2, oInnerArea.X + oInnerArea.Width - 2, oInnerArea.Y + oInnerArea.Height - 2)

        '---bottom
        g.DrawLine(New Pen(oBorderColorD), oInnerArea.X + 1, oInnerArea.Y + oInnerArea.Height, oInnerArea.X + oInnerArea.Width, oInnerArea.Y + oInnerArea.Height)
        g.DrawLine(New Pen(oBorderColorL), oInnerArea.X + 1, oInnerArea.Y + oInnerArea.Height - 1, oInnerArea.X + oInnerArea.Width - 1, oInnerArea.Y + oInnerArea.Height - 1)
        g.DrawLine(New Pen(oBorderColorL), oInnerArea.X + 2, oInnerArea.Y + oInnerArea.Height - 2, oInnerArea.X + oInnerArea.Width - 1, oInnerArea.Y + oInnerArea.Height - 2)

        Dim oSize As SizeF = g.MeasureString(msTitle, moTitleFont)

        '---Center legend title
        Dim iCurX As Integer = oInnerArea.X + LegendTextXPos()
        Dim iCurY As Integer = oInnerArea.Y + miTopMargin + oSize.Height * 1.5

        g.DrawString( _
                msTitle, _
                moTitleFont, _
                New SolidBrush(moLegendColor), _
                oInnerArea.X + oInnerArea.Width / 2 - oSize.Width / 2, _
                oInnerArea.Y + miTopMargin)

        Dim oIndicator As Indicator
        Dim oIndicatorFactory As New Overlay.IndicatorFactory

        For Each oEntry As LegendEntry In moAllSeries
            oSize = g.MeasureString(oEntry.Name, moLegendFont)

            g.DrawString(oEntry.Name, moLegendFont, Brushes.Black, iCurX, iCurY)

            oIndicator = oEntry.LegendIndicator
            oIndicatorFactory.Draw(oIndicator, g, oInnerArea.X + IndicatorXPos(), iCurY + oSize.Height / 2)

            iCurY = iCurY + oSize.Height
        Next
    End Sub

    Public Sub SaveLegendToFile( _
                    ByVal sPath As String, _
                    ByVal iType As System.Drawing.Imaging.ImageFormat)

        If msFileName = "" Then
            msFileName = msUniqueName & "." & iType.ToString
        End If

        Dim oPicture As New Bitmap( _
                                moChartDimensions.OuterWidth, _
                                moChartDimensions.OuterHeight, _
                                System.Drawing.Imaging.PixelFormat.Format24bppRgb)

        '---Fill in background color
        Dim g As Graphics = Graphics.FromImage(oPicture)
        g.FillRectangle(New SolidBrush(moChartDimensions.BackgroundColor), _
                        New Rectangle(0, 0, moChartDimensions.OuterWidth, moChartDimensions.OuterHeight))

        DrawLegend(Graphics.FromImage(oPicture))

        Try
            oPicture.Save(sPath & msFileName, iType)

        Catch oEX As System.Runtime.InteropServices.ExternalException
            Dim oChartEx As New ChartException("Unable to create chart image. Directory may not exist or process may lack required permissions to directory.")
            Throw oChartEx
        End Try
    End Sub

    Private Function ComputeLegendArea() As Rectangle
        Dim iLBoxX As Integer = 0
        Dim iLBoxY As Integer = 0

        Dim oRect As Rectangle = Nothing

        Select Case miLegendPosition
            Case Position.East
                '---X=Flush against Right-hand side
                '---Y=Centered
                iLBoxX = moChartDimensions.FrameWidthRight - OuterWidth()
                iLBoxY = moChartDimensions.OuterY + (moChartDimensions.OuterHeight / 2) - (OuterHeight() / 2)

                oRect = New Rectangle( _
                            moChartDimensions.OuterWidth - moChartDimensions.FrameWidthRight + iLBoxX, _
                            iLBoxY, _
                            OuterWidth, _
                            OuterHeight)

            Case Position.West
                '---X=Flush against left-hand side
                '---Y=Centered
                iLBoxX = 0
                iLBoxY = moChartDimensions.OuterY + (moChartDimensions.OuterHeight / 2) - (OuterHeight() / 2)

                oRect = New Rectangle( _
                            iLBoxX, _
                            iLBoxY, _
                            OuterWidth, _
                            OuterHeight)

            Case Position.South
                '---X=Flush against left-hand side
                '---Y=Centered
                iLBoxX = 0
                iLBoxY = moChartDimensions.OuterY + moChartDimensions.FrameWidthBottom - OuterHeight()

                oRect = New Rectangle( _
                            iLBoxX, _
                            moChartDimensions.OuterHeight - moChartDimensions.FrameWidthBottom + iLBoxY, _
                            OuterWidth, _
                            OuterHeight)

            Case Position.North
                '---X=Flush against left-hand side
                '---Y=Centered
                iLBoxX = (moChartDimensions.OuterWidth / 2) - (OuterWidth() / 2)
                iLBoxY = moChartDimensions.OuterY

                oRect = New Rectangle( _
                            iLBoxX, _
                            iLBoxY, _
                            OuterWidth, _
                            OuterHeight)

            Case Position.NorthEast
                '---X=Flush against right-hand side
                '---Y=Flush against top
                iLBoxX = moChartDimensions.FrameWidthRight - OuterWidth()
                iLBoxY = moChartDimensions.OuterY

                oRect = New Rectangle( _
                            moChartDimensions.OuterWidth - moChartDimensions.FrameWidthRight + iLBoxX, _
                            iLBoxY, _
                            OuterWidth, _
                            OuterHeight)

            Case Position.NorthWest
                '---X=Flush against left-hand side
                '---Y=Flush against top
                iLBoxX = 0
                iLBoxY = moChartDimensions.OuterY

                oRect = New Rectangle( _
                            iLBoxX, _
                            iLBoxY, _
                            OuterWidth, _
                            OuterHeight)

            Case Position.SouthEast
                '---X=Flush against right-hand side
                '---Y=Flush against bottom
                iLBoxX = moChartDimensions.OuterWidth - OuterWidth()
                iLBoxY = moChartDimensions.OuterY + moChartDimensions.OuterHeight - OuterHeight()

                oRect = New Rectangle( _
                            iLBoxX, _
                            iLBoxY, _
                            OuterWidth, _
                            OuterHeight)

            Case Position.SouthWest
                '---X=Flush against left-hand side
                '---Y=Flush against bottom
                iLBoxX = 0
                iLBoxY = moChartDimensions.OuterY + moChartDimensions.OuterHeight - OuterHeight()

                oRect = New Rectangle( _
                            iLBoxX, _
                            iLBoxY, _
                            OuterWidth, _
                            OuterHeight)

            Case Position.Center
                oRect = New Rectangle( _
                            0, _
                            0, _
                            moChartDimensions.OuterWidth, _
                            moChartDimensions.OuterHeight)
        End Select

        Return oRect
    End Function

    Private Class LegendEntry
        Private msName As String
        Private moIndicator As Indicator

        Public Sub New( _
            ByVal sName As String, _
            ByVal oIndicatorType As jChart.Indicator.Type, _
            ByVal oColor As System.Drawing.Color)

            msName = sName
            moIndicator = New Indicator(oIndicatorType, oColor)
        End Sub

        Public Sub New( _
            ByVal oSeries As Object)

            msName = oSeries.Name

            If TypeOf oSeries Is Overlay.Series.AreaSeries Then
                moIndicator = New Indicator(Indicator.Type.UpArrow, oSeries.Color, 10)

            ElseIf TypeOf oSeries Is Overlay.Series.BarSeries Then
                moIndicator = New Indicator(Indicator.Type.UpArrow, oSeries.Color, 10)

            ElseIf TypeOf oSeries Is Overlay.Series.PieSeries Then
                moIndicator = New Indicator(Indicator.Type.Circle, oSeries.Color, 10)

            ElseIf TypeOf oSeries Is Overlay.Series.ScatterSeries Then
                moIndicator = oSeries.Indicator

            ElseIf TypeOf oSeries Is Overlay.Series.StackedAreaSeries Then
                moIndicator = New Indicator(Indicator.Type.UpArrow, oSeries.Color, 10)

            ElseIf TypeOf oSeries Is Overlay.Series.StackedBarSeries Then
                moIndicator = New Indicator(Indicator.Type.UpArrow, oSeries.Color, 10)

            ElseIf TypeOf oSeries Is Overlay.Series.LineSeries Then
                If Not oSeries.indicator Is Nothing Then
                    moIndicator = oSeries.indicator
                Else
                    moIndicator = New Indicator(Indicator.Type.Rectangle, oSeries.Color, 10)
                End If

            Else
                '---Use rectangle if series type not known
                moIndicator = New Indicator(Indicator.Type.Box, oSeries.Color, 10)
            End If

        End Sub

        Public Property Name() As String
            Get
                Return msName
            End Get
            Set(ByVal value As String)
                msName = value
            End Set
        End Property

        Public Property LegendIndicator() As Indicator
            Get
                Return moIndicator
            End Get
            Set(ByVal value As Indicator)
                moIndicator = value
            End Set
        End Property
    End Class
End Class

